/*
 * Copyright (C) 2016 Evgenii Zagumennyi
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.github.zagum.expandicon;

import ohos.agp.animation.Animator;
import ohos.agp.animation.AnimatorValue;
import ohos.agp.colors.RgbColor;
import ohos.agp.components.AttrSet;
import ohos.agp.components.Component;
import ohos.agp.render.Canvas;
import ohos.agp.render.Paint;
import ohos.agp.render.Path;
import ohos.agp.utils.Color;
import ohos.agp.utils.Point;
import ohos.app.Context;

public class ExpandIconView extends Component implements Component.DrawTask {
    private static final float THICKNESS_PROPORTION = 5f / 36f;
    private static final float PADDING_PROPORTION = 4f / 24f;
    private static final long DEFAULT_ANIMATION_DURATION = 150;

    public static final int MORE = 0;
    public static final int LESS = 1;
    public static final int INTERMEDIATE = 2;
    private int state = MORE;
    private float fraction;
    private float currentFraction;
    private AnimatorValue animatorValue;
    private long animationDuration;
    private Paint paint = new Paint();
    private Path path = new Path();

    private boolean switchColor = false;
    private int paintColor;
    private int color = Color.BLACK.getValue();
    private int colorMore;
    private int colorLess;
    private int colorIntermediate;
    private boolean roundedCorners = false;
    private int padding;
    private boolean useDefaultPadding;
    private float thickness;
    private int lineWidth;

    public ExpandIconView(Context context) {
        this(context, null, null);
    }

    public ExpandIconView(Context context, AttrSet attrSet) {
        this(context, attrSet, null);
    }

    public ExpandIconView(Context context, AttrSet attrSet, String styleName) {
        super(context, attrSet, styleName);
        init(attrSet);
    }

    private void init(AttrSet attrSet) {
        roundedCorners = AttrUtils.getBooleanFromAttr(attrSet, "eiv_roundedCorners", false);
        switchColor = AttrUtils.getBooleanFromAttr(attrSet, "eiv_switchColor", false);
        color = AttrUtils.getColorFromAttr(attrSet, "eiv_color", Color.BLACK.getValue());
        colorMore = AttrUtils.getColorFromAttr(attrSet, "eiv_colorMore", Color.BLACK.getValue());
        colorLess = AttrUtils.getColorFromAttr(attrSet, "eiv_colorLess", Color.BLACK.getValue());
        colorIntermediate = AttrUtils.getColorFromAttr(attrSet, "eiv_colorIntermediate", -1);
        animationDuration = AttrUtils.getLongFromAttr(attrSet, "eiv_animationDuration", DEFAULT_ANIMATION_DURATION);
        lineWidth = AttrUtils.getDimensionFromAttr(attrSet, "eiv_lineWidth", -1);
        padding = AttrUtils.getDimensionFromAttr(attrSet, "eiv_padding", -1);
        useDefaultPadding = (padding == -1);

        paintColor = color;
        paint.setAntiAlias(true);
        paint.setStrokeWidth(dp2px(4));
        paint.setColor(new Color(paintColor));
        paint.setStyle(Paint.Style.STROKE_STYLE);
        paint.setDither(true);
        if (roundedCorners) {
            paint.setStrokeJoin(Paint.Join.ROUND_JOIN);
            paint.setStrokeCap(Paint.StrokeCap.ROUND_CAP);
        }
        addDrawTask(this);
    }

    @Override
    public void onDraw(Component component, Canvas canvas) {
        calculateArrowMetrics(getWidth(), getHeight());

        float width = getWidth() - padding * 2;
        float height = getHeight() - padding * 2;
        canvas.translate(padding, padding);

        float angle = 90 * currentFraction - 45;

        float startY = height / 2 - width / 4 * (float) Math.sin(Math.toRadians(angle));
        float startX = width / 2;
        float endY = startY + width / 2 * (float) Math.sin(Math.toRadians(angle));
        float endX1 = startX - width / 2 * (float) Math.cos(Math.toRadians(angle));
        float endX2 = startX + width / 2 * (float) Math.cos(Math.toRadians(angle));

        if (!switchColor) {
            paint.setColor(new Color(color));
        } else if (colorIntermediate == -1) {
            paint.setColor(
                    new Color(getAnimateColor(colorMore, colorLess, currentFraction).asArgbInt()));
        } else if (colorIntermediate != -1) {
            if (currentFraction < 0.5f) {
                paint.setColor(
                        new Color(
                                getAnimateColor(colorMore, colorIntermediate, currentFraction * 2)
                                        .asArgbInt()));
            } else {
                paint.setColor(
                        new Color(
                                getAnimateColor(
                                        colorIntermediate,
                                        colorLess,
                                        (currentFraction - 0.5f) * 2)
                                        .asArgbInt()));
            }
        }
        path.reset();
        path.moveTo(new Point(endX1, endY));
        path.lineTo(new Point(startX, startY));
        path.lineTo(new Point(endX2, endY));
        canvas.drawPath(path, paint);
    }


    private void updateArrow(boolean animate) {
        final float lastFraction = currentFraction;
        if (animate) {
            if (animatorValue == null) {
                animatorValue = new AnimatorValue();
                animatorValue.setCurveType(Animator.CurveType.LINEAR);
            } else if (animatorValue.isRunning()) {
                animatorValue.stop();
            }
            long duration = (long) ((Math.abs(fraction - currentFraction)) * animationDuration);
            animatorValue.setDuration(duration);
            animatorValue.setValueUpdateListener(
                    new AnimatorValue.ValueUpdateListener() {
                        @Override
                        public void onUpdate(AnimatorValue animatorValue, float v) {
                            currentFraction = lastFraction + (fraction - lastFraction) * v;
                            getContext().getUITaskDispatcher().asyncDispatch(() -> invalidate());
                        }
                    });
            animatorValue.start();
        } else {
            if (animatorValue != null && animatorValue.isRunning()) {
                animatorValue.stop();
            }
            currentFraction = fraction;
            invalidate();
        }
    }

    private void calculateArrowMetrics(int width, int height) {
        final int arrowMaxWidth = (height >= width ? width : height);
        if (useDefaultPadding) {
            padding = (int) (PADDING_PROPORTION * arrowMaxWidth);
        }
        final int arrowWidth = arrowMaxWidth - 2 * padding;
        if (lineWidth == -1) {
            thickness = (int) (arrowWidth * THICKNESS_PROPORTION);
            paint.setStrokeWidth(thickness);
        } else {
            paint.setStrokeWidth(lineWidth);
        }
    }

    private int getFinalStateByFraction() {
        if (fraction <= .5f) {
            return MORE;
        } else {
            return LESS;
        }
    }

    public void switchState() {
        switchState(true);
    }

    /**
     * Changes state and updates view
     *
     * @param animate Indicates thaw state will be changed with animation or not
     */
    public void switchState(boolean animate) {
        final int newState;
        switch (state) {
            case MORE:
                newState = LESS;
                break;
            case LESS:
                newState = MORE;
                break;
            case INTERMEDIATE:
                newState = getFinalStateByFraction();
                break;
            default:
                throw new IllegalArgumentException("Unknown state [" + state + "]");
        }
        setState(newState, animate);
    }

    /**
     * Set current fraction for arrow and updates view
     *
     * @param fraction Must be value from 0f to 1f {@link #MORE} state value is 0f, {@link #LESS}
     *                 state value is 1f
     * @param animate  with animate
     * @throws IllegalArgumentException if fraction is less than 0f or more than 1f
     */
    public void setFraction(float fraction, boolean animate) {
        if (fraction < 0f || fraction > 1f) {
            throw new IllegalArgumentException("Fraction value must be from 0 to 1f, fraction=" + fraction);
        }

        if (this.fraction == fraction) {
            return;
        }

        this.fraction = fraction;
        if (fraction == 0f) {
            state = MORE;
        } else if (fraction == 1f) {
            state = LESS;
        } else {
            state = INTERMEDIATE;
        }

        updateArrow(animate);
    }

    /**
     * Set duration of icon animation from state {@link #MORE} to state {@link #LESS}
     *
     * @param animationDuration animation duration time
     */
    public void setAnimationDuration(long animationDuration) {
        this.animationDuration = animationDuration;
    }

    /**
     * Set one of two states and updates view
     *
     * @param state   {@link #MORE} or {@link #LESS}
     * @param animate Indicates thaw state will be changed with animation or not
     * @throws IllegalArgumentException if {@param state} is invalid
     */
    public void setState(int state, boolean animate) {
        this.state = state;
        if (state == MORE) {
            fraction = 0f;
        } else if (state == LESS) {
            fraction = 1f;
        } else {
            throw new IllegalArgumentException("Unknown state, must be one of STATE_MORE = 0,  STATE_LESS = 1");
        }
        updateArrow(animate);
    }

    private RgbColor getAnimateColor(int from, int to, float v) {
        RgbColor a = RgbColor.fromArgbInt(from);
        RgbColor b = RgbColor.fromArgbInt(to);
        int red = (int) (a.getRed() + (b.getRed() - a.getRed()) * v);
        int blue = (int) (a.getBlue() + (b.getBlue() - a.getBlue()) * v);
        int green = (int) (a.getGreen() + (b.getGreen() - a.getGreen()) * v);
        int alpha = (int) (a.getAlpha() + (b.getAlpha() - a.getAlpha()) * v);
        RgbColor mCurrentColorRgb = new RgbColor(red, green, blue, alpha);
        return mCurrentColorRgb;
    }

    private int dp2px(float dp) {
        return (int) (getResourceManager().getDeviceCapability().screenDensity / 160 * dp);
    }
}
